package at.ac.tuwien.dse.health.event.listener;

import at.ac.tuwien.dse.health.event.bus.EventTarget;
import at.ac.tuwien.dse.health.event.type.Event;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.google.common.util.concurrent.Striped;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.io.Serializable;
import java.util.Collection;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;

import static org.springframework.util.ObjectUtils.isEmpty;

/**
 * Enqueue all {@link Event Events} for later delivery by a provided {@link EventListener}.
 * <p/>
 * If there is no delegate {@link EventListener} available, a {@link LocalEventListener} is used instead.
 *
 * @author Bernhard Nickel
 * @author Gregor Schauer
 * @author Dominik Strasser
 */
public class QueueEventListener implements EventListener {
	public static final Logger LOGGER = LogManager.getLogger(QueueEventListener.class);
	protected Multimap<EventTarget, Event<? extends Serializable>> eventMap = LinkedHashMultimap.create();
	protected ReadWriteLock lock = Striped.readWriteLock(1).get(this);
	protected EventListener delegateListener;

	@Override
	public void handleEvent(EventTarget eventTarget, Event<? extends Serializable> event) {
		Lock readLock = lock.readLock();
		readLock.lock();
		eventMap.put(eventTarget, event);
		readLock.unlock();
	}

	/**
	 * Sends all {@link Event Events} scheduled for delivery so far to the {@link EventTarget EventTargets}.
	 * <p/>
	 * After all {@link Event Events} of an {@link EventTarget} are delivered (or failed to be delivered), all
	 * {@link Event Events} are discarded.
	 * <p/>
	 * If {@code eventTargets} is {@code null} or empty, all {@link Event Events} are delivered instead.
	 *
	 * @param eventTargets the targets that should receive the events (can be {@code null})
	 */
	public void send(EventTarget... eventTargets) {
		lock.writeLock().lock();
		// Deliver all events
		Iterable<EventTarget> targets = isEmpty(eventTargets) ? eventMap.keySet() : Lists.newArrayList(eventTargets);
		for (EventTarget eventTarget : targets) {
			for (Event<? extends Serializable> event : eventMap.get(eventTarget)) {
				try {
					getDelegateListener().handleEvent(eventTarget, event);
				} catch (Exception e) {
					LOGGER.catching(e);
				}
			}
		}

		// Discard the events delivered
		if (isEmpty(eventTargets)) {
			eventMap.clear();
		} else {
			for (EventTarget eventTarget : eventTargets) {
				eventMap.removeAll(eventTarget);
			}
		}
		lock.writeLock().unlock();
	}

	/**
	 * Returns all {@link Event Events} delivered to the given target.
	 *
	 * @param target the event target
	 * @return the events
	 */
	public Collection<Event<? extends Serializable>> getEvents(EventTarget target) {
		return eventMap.get(target);
	}

	public Multimap<EventTarget, Event<? extends Serializable>> getEventMap() {
		return eventMap;
	}

	protected EventListener getDelegateListener() {
		if (delegateListener == null) {
			delegateListener = new LocalEventListener();
		}
		return delegateListener;
	}

	public void setDelegateListener(EventListener delegateListener) {
		this.delegateListener = delegateListener;
	}
}
